import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:fluttercommon/src/widgets/cupertino_loading_widget.dart';

import '../customizable.dart';
import '../widgets/http_request_failed.dart';
import 'http_request_empty.dart';

class PullRefreshNotification extends Notification {}

class RemoveMeNotification<T> extends Notification {
  final T item;

  RemoveMeNotification(this.item);
}

class UpdateMeNotification<T> extends Notification {
  final T item;

  UpdateMeNotification(this.item);
}

class PageData<T> {
  final bool success;
  final List<T>? items;
  final String? message;
  final int? code;

  const PageData._(this.success, this.items, this.message, this.code);

  const PageData.success(List<T>? items) : this._(true, items, null, null);

  const PageData.failed(String? message, [int? code])
      : this._(false, null, message, code);
}

typedef PageBuilder<T> = Future<PageData<T>> Function(int page, int rows);

/// Signature for a function that creates a widget for a given item of type 'T'.
typedef ItemWidgetBuilder<T> = Widget Function(int index, T item);

/// A scrollable list which implements pagination.
///
/// When scrolled to the end of the list [Pagination] calls [pageBuilder] which
/// must be implemented which returns a Future List of type 'T'.
///
/// [itemBuilder] creates widget instances on demand.
class PagedListView<T> extends StatefulWidget {
  /// Creates a scrollable, paginated, linear array of widgets.
  ///
  /// The arguments [pageBuilder], [itemBuilder] must not be null.
  PagedListView({
    Key? key,
    required this.pageBuilder,
    required this.itemBuilder,
    this.rows = 20,
    this.scrollDirection = Axis.vertical,
    this.progress,
    this.onError,
    this.reverse = false,
    this.controller,
    this.primary,
    this.physics,
    this.padding,
    this.itemExtent,
    this.cacheExtent,
    this.semanticChildCount,
    this.noDataWidget,
    this.header,
    this.footer,
    this.supportPullRefresh = true,
    this.onRefreshStart,
  })  : assert(rows > 1),
        super(key: key);

  /// Called when the list scrolls to an end
  ///
  /// Function should return Future List of type 'T'
  final PageBuilder<T> pageBuilder;

  /// Called to build children for [Pagination]
  ///
  /// Function should return a widget
  final ItemWidgetBuilder<T> itemBuilder;

  /// Scroll direction of list view
  final Axis scrollDirection;

  /// When non-null [progress] widget is called to show loading progress
  final Widget? progress;

  /// Handle error returned by the Future implemented in [pageBuilder]
  final Function(dynamic error)? onError;

  /// rows per page.
  final int rows;

  final Widget? noDataWidget;

  final bool reverse;
  final ScrollController? controller;
  final bool? primary;
  final ScrollPhysics? physics;
  final bool shrinkWrap = false;
  final EdgeInsetsGeometry? padding;
  final double? itemExtent;
  final bool addAutomaticKeepAlive = true;
  final bool addRepaintBoundaries = true;
  final bool addSemanticIndexes = true;
  final double? cacheExtent;
  final int? semanticChildCount;
  final bool supportPullRefresh;

  final Widget? header;
  final Widget? footer;
  final Function? onRefreshStart;

  @override
  PagedListViewState<T> createState() => PagedListViewState<T>();
}

class PagedListViewState<T> extends State<PagedListView<T>> {
  final List<T> _list = [];
  bool _isLoading = false;
  bool _isEndOfList = false;

  bool get hasNoData => _list.isEmpty && _isEndOfList;

  bool get hasError => _errorMessage != null && _list.isEmpty;
  String? _errorMessage;
  int? _errorCode;

  List<T> get dataList => _list;

  late ScrollController _scrollController;

  final ValueNotifier<double> remainingSpace = ValueNotifier(0);

  void remove(T item) {
    if (_list.remove(item) == true) {
      setState(() {});
    }
  }

  void update(T item) async {
    final at = _list.indexOf(item);
    if (at >= 0) {
      final PageData<T> pagedData = await widget.pageBuilder(at + 1, 1);
      if (pagedData.success && pagedData.items!.length == 1) {
        _list.replaceRange(at, at + 1, pagedData.items!);
        setState(() {});
      }
    }
  }

  Future fetchMore() {
    print("fetchMore ... loading ? $_isLoading end? $_isEndOfList");
    if (!_isLoading) {
      _isLoading = true;
      return widget.pageBuilder(_getPage(), widget.rows).then((pageData) {
        _isLoading = false;
        if (pageData.success) {
          if (pageData.items?.isEmpty ?? true) {
            if (mounted) {
              _isEndOfList = true;
              setState(() {});
            }
          } else {
            if (mounted) {
              if (pageData.items!.length < widget.rows) {
                _isEndOfList = true;
              }
              _list.addAll(pageData.items!);
              setState(() {});
            }
          }
        } else {
          if (mounted) {
            setState(() {
              _errorMessage = pageData.message ?? "请求失败";
              _errorCode = pageData.code;
            });
          }
          debugPrint("pageData: $pageData");
        }
      }).catchError((error) {
        if (mounted) {
          _isEndOfList = true;
          setState(() {});
        }
        debugPrint(error.toString());
        if (widget.onError != null) {
          widget.onError!(error);
        }
      });
    }
    return Future.value(null);
  }

  int _getPage() {
    if (_list.isEmpty) {
      return 1;
    }

    return _list.length ~/ widget.rows + 1;
  }

  @override
  void initState() {
    super.initState();
    _scrollController = widget.controller ?? ScrollController();
    fetchMore();
  }

  @override
  Widget build(BuildContext context) {
    if (widget.supportPullRefresh == false) {
      return hasError
          ? _createErrorWidget(context)
          : (hasNoData ? _buildNoDataView(context) : buildListViewWrapper());
    }
    return RefreshIndicator(
      child: hasError
          ? CustomScrollView(
              slivers: <Widget>[
                SliverFillRemaining(
                  child: _createErrorWidget(context),
                ),
              ],
            )
          : (hasNoData ? _buildNoDataView(context) : buildListViewWrapper()),
      onRefresh: () async {
        if (widget.onRefreshStart != null) {
          widget.onRefreshStart!();
        }
        dataList.clear();
        _isEndOfList = false;
        await fetchMore();
        PullRefreshNotification().dispatch(context);
      },
    );
  }

  Widget buildListViewWrapper() {
    if (dataList.isEmpty && _isLoading) {
      return Center(child: widget.progress ?? CupertinoLoadingWidget());
    }
    return NotificationListener(
      child: buildListView(),
      onNotification: (notification) {
        if (notification is RemoveMeNotification) {
          final item = notification.item;
          if (item is T) {
            remove(notification.item);
            return true;
          }
        } else if (notification is UpdateMeNotification) {
          final item = notification.item;
          if (item is T) {
            update(notification.item);
            return true;
          }
        }
        return false;
      },
    );
  }

  ListView buildListView() {
    return ListView.custom(
      padding: widget.padding,
      controller: _scrollController,
      physics: widget.physics ?? const ClampingScrollPhysics(),
      primary: widget.primary,
      reverse: widget.reverse,
      shrinkWrap: widget.shrinkWrap,
      itemExtent: widget.itemExtent,
      cacheExtent: widget.cacheExtent,
      scrollDirection: widget.scrollDirection,
      keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
      childrenDelegate: SliverChildBuilderDelegate(
          (context, index) => buildListItem(context, index)),
    );
  }

  Widget? buildListItem(BuildContext context, int position) {
    int newPosition = widget.header == null ? position : position - 1;
    if (position == 0 && widget.header != null) {
      return widget.header!;
    } else if (newPosition < _list.length) {
      return widget.itemBuilder(newPosition, _list[newPosition]);
    } else {
      if (_isEndOfList) {
        return newPosition == dataList.length ? buildLastWidget(context) : null;
      } else {
        fetchMore();
        if (_isLoading) return null;
        return widget.progress ?? CupertinoLoadingWidget();
      }
    }
  }

  Widget buildLastWidget(BuildContext context) {
    if (_scrollController.offset <= 0) {
      Future.delayed(Duration(milliseconds: 100), () {
        if (!mounted) return;
        final renderSliver = context.findRenderObject() as RenderSliver?;
        if (renderSliver != null) {
          double children = 0;
          renderSliver.visitChildren((child) {
            children += child.paintBounds.height;
          });

          final viewportMainAxisExtent =
              renderSliver.constraints.viewportMainAxisExtent;
          if (viewportMainAxisExtent > children) {
            remainingSpace.value =
                renderSliver.constraints.viewportMainAxisExtent - children;
          }
        }
      });
    }
    return _scrollController.offset > 0
        ? lastPromptWidget(context)
        : ValueListenableBuilder<double>(
            valueListenable: remainingSpace,
            builder: (context, value, child) {
              return SizedBox(height: value);
            },
          );
  }

  Widget lastPromptWidget(BuildContext context) {
    return Align(
      alignment: Alignment.center,
      child: Padding(
        padding: const EdgeInsets.only(top: 8.0, bottom: 16),
        child: widget.footer ??
            Text(
              CustomizableFeatures.strings.list_end_prompt,
              style: const TextStyle(color: Color(0xFF999999), fontSize: 14),
            ),
      ),
    );
  }

  Widget _createErrorWidget(BuildContext context) {
    return CustomizableFeatures.errorWidgetBuilder?.call(
          context,
          _errorMessage ?? "",
          _errorCode,
        ) ??
        RequestFailedWidget(description: _errorMessage);
  }

  Widget _buildNoDataView(BuildContext context) {
    Widget noData = widget.noDataWidget ?? NoDataWidget();
    if (widget.header != null) {
      return ListView(
        children: <Widget>[
          widget.header!,
          noData,
        ],
      );
    }
    return CustomScrollView(
      slivers: <Widget>[
        SliverFillRemaining(
          child: noData,
        ),
      ],
    );
  }
}
